a tool for shared writing and social publishing
1import { z } from "zod";
2import {
3 PullRequest,
4 PullResponseV1,
5 VersionNotSupportedResponse,
6} from "replicache";
7import type { Fact } from "src/replicache";
8import { FactWithIndexes } from "src/replicache/utils";
9import type { Attribute } from "src/replicache/attributes";
10import { makeRoute } from "../lib";
11import type { Env } from "./route";
12
13// First define the sub-types for V0 and V1 requests
14const pullRequestV0 = z.object({
15 pullVersion: z.literal(0),
16 schemaVersion: z.string(),
17 profileID: z.string(),
18 cookie: z.any(), // ReadonlyJSONValue
19 clientID: z.string(),
20 lastMutationID: z.number(),
21});
22
23// For the Cookie type used in V1
24const cookieType = z.union([
25 z.null(),
26 z.string(),
27 z.number(),
28 z
29 .object({
30 order: z.union([z.string(), z.number()]),
31 })
32 .and(z.record(z.string(), z.any())), // ReadonlyJSONValue with order property
33]);
34
35const pullRequestV1 = z.object({
36 pullVersion: z.literal(1),
37 schemaVersion: z.string(),
38 profileID: z.string(),
39 cookie: cookieType,
40 clientGroupID: z.string(),
41});
42
43// Combined PullRequest type
44const PullRequestSchema = z.union([pullRequestV0, pullRequestV1]);
45
46export const pull = makeRoute({
47 route: "pull",
48 input: z.object({ pullRequest: PullRequestSchema, token_id: z.string() }),
49 handler: async ({ pullRequest, token_id }, { supabase }: Env) => {
50 let body = pullRequest;
51 if (body.pullVersion === 0) return versionNotSupported;
52 let { data, error } = await supabase.rpc("pull_data", {
53 token_id,
54 client_group_id: body.clientGroupID,
55 });
56 if (!data) {
57 console.log(error);
58
59 return {
60 error: "ClientStateNotFound",
61 } as const;
62 }
63
64 let facts = data.facts as {
65 attribute: string;
66 created_at: string;
67 data: any;
68 entity: string;
69 id: string;
70 updated_at: string | null;
71 version: number;
72 }[];
73 let publication_data = data.publications as {
74 description: string;
75 title: string;
76 tags: string[];
77 cover_image: string | null;
78 }[];
79 let pub_patch = publication_data?.[0]
80 ? [
81 {
82 op: "put",
83 key: "publication_description",
84 value: publication_data[0].description,
85 },
86 {
87 op: "put",
88 key: "publication_title",
89 value: publication_data[0].title,
90 },
91 {
92 op: "put",
93 key: "publication_tags",
94 value: publication_data[0].tags || [],
95 },
96 {
97 op: "put",
98 key: "publication_cover_image",
99 value: publication_data[0].cover_image || null,
100 },
101 ]
102 : [];
103
104 let clientGroup = (
105 (data.client_groups as {
106 client_id: string;
107 client_group: string;
108 last_mutation: number;
109 }[]) || []
110 ).reduce(
111 (acc, clientRecord) => {
112 acc[clientRecord.client_id] = clientRecord.last_mutation;
113 return acc;
114 },
115 {} as { [clientID: string]: number },
116 );
117
118 return {
119 cookie: Date.now(),
120 lastMutationIDChanges: clientGroup,
121 patch: [
122 { op: "clear" },
123 { op: "put", key: "initialized", value: true },
124 ...(facts || []).map((f) => {
125 return {
126 op: "put",
127 key: f.id,
128 value: FactWithIndexes(f as unknown as Fact<Attribute>),
129 } as const;
130 }),
131 ...pub_patch,
132 ],
133 } as PullResponseV1;
134 },
135});
136
137const versionNotSupported: VersionNotSupportedResponse = {
138 error: "VersionNotSupported",
139 versionType: "pull",
140};